13. Look Away: The Code
Look Away: The Code
Creating the empty
look_away
node script
The steps that you should take to create the
look_away
node are exactly the same as the steps you took to create the
simple_mover
and
arm_mover
scripts, but of course change the actual name of the file itself.
Open a new terminal, and type the following:
$ cd /home/workspace/catkin_ws/src/simple_arm/src/
$ gedit look_away.cpp
You have created and opened the C++
look_away
file with the
gedit
editor. Now copy and paste the code below and save the file.
look_away.cpp
#include "ros/ros.h"
#include "simple_arm/GoToPosition.h"
#include <sensor_msgs/JointState.h>
#include <sensor_msgs/Image.h>
// Define global vector of joints last position, moving state of the arm, and the client that can request services
std::vector<double> joints_last_position{ 0, 0 };
bool moving_state = false;
ros::ServiceClient client;
// This function calls the safe_move service to safely move the arm to the center position
void move_arm_center()
{
ROS_INFO_STREAM("Moving the arm to the center");
// Request centered joint angles [1.57, 1.57]
simple_arm::GoToPosition srv;
srv.request.joint_1 = 1.57;
srv.request.joint_2 = 1.57;
// Call the safe_move service and pass the requested joint angles
if (!client.call(srv))
ROS_ERROR("Failed to call service safe_move");
}
// This callback function continuously executes and reads the arm joint angles position
void joint_states_callback(const sensor_msgs::JointState js)
{
// Get joints current position
std::vector<double> joints_current_position = js.position;
// Define a tolerance threshold to compare double values
double tolerance = 0.0005;
// Check if the arm is moving by comparing its current joints position to its latest
if (fabs(joints_current_position[0] - joints_last_position[0]) < tolerance && fabs(joints_current_position[1] - joints_last_position[1]) < tolerance)
moving_state = false;
else {
moving_state = true;
joints_last_position = joints_current_position;
}
}
// This callback function continuously executes and reads the image data
void look_away_callback(const sensor_msgs::Image img)
{
bool uniform_image = true;
// Loop through each pixel in the image and check if its equal to the first one
for (int i = 0; i < img.height * img.step; i++) {
if (img.data[i] - img.data[0] != 0) {
uniform_image = false;
break;
}
}
// If the image is uniform and the arm is not moving, move the arm to the center
if (uniform_image == true && moving_state == false)
move_arm_center();
}
int main(int argc, char** argv)
{
// Initialize the look_away node and create a handle to it
ros::init(argc, argv, "look_away");
ros::NodeHandle n;
// Define a client service capable of requesting services from safe_move
client = n.serviceClient<simple_arm::GoToPosition>("/arm_mover/safe_move");
// Subscribe to /simple_arm/joint_states topic to read the arm joints position inside the joint_states_callback function
ros::Subscriber sub1 = n.subscribe("/simple_arm/joint_states", 10, joint_states_callback);
// Subscribe to rgb_camera/image_raw topic to read the image data inside the look_away_callback function
ros::Subscriber sub2 = n.subscribe("rgb_camera/image_raw", 10, look_away_callback);
// Handle ROS communication events
ros::spin();
return 0;
}
Look Away: The Code
The code: Explained
#include "ros/ros.h"
#include "simple_arm/GoToPosition.h"
#include <sensor_msgs/JointState.h>
#include <sensor_msgs/Image.h>
The header files are similar to those in
arm_mover
, except this time we included the
JointState.h
header file so that we can read the arm joints’ positions. We also include the
Image.h
header file so that we can use the camera data.
ros::init(argc, argv, "look_away");
ros::NodeHandle n;
Inside the C++ main function, the
look_away
node is initialized and a ROS NodeHandle object
n
is instantiated to communicate with ROS.
client = n.serviceClient<simple_arm::GoToPosition>("/arm_mover/safe_move");
A
client
object is created here. This object can request
GoToPosition
services from the
/arm_mover/safe_move
service created earlier in the
arm_mover
node. This client object is defined globally in the code, so we can request services within any function. In particular, this happens in the
move_arm_center()
function.
ros::Subscriber sub1 = n.subscribe("/simple_arm/joint_states", 10, joint_states_callback);
The first subscriber object
sub1
, subscribes to the
/simple_arm/joint_states
topic. By subscribing to this topic, we can track the arm position by reading the angle of each joint. The
queue_size
is set to 10, meaning that a maximum of 10 messages can be stored in the queue. The data from each new incoming message is passed to the
joint_states_callback
function.
ros::Subscriber sub2 = n.subscribe("rgb_camera/image_raw", 10, look_away_callback);
The second subscriber object
sub2
, subscribes to the
/rgb_camera/image_raw
topic. The
queue_size
is also set to 10. And the
look_away_callback
function is called each time a new message arrives.
ros::spin();
The
ros::spin()
function simply blocks until a shutdown request is received by the node.
void joint_states_callback(const sensor_msgs::JointState js)
{
// Get joints current position
std::vector<double> joints_current_position = js.position;
// Define a tolerance threshold to compare double values
double tolerance = 0.0005;
// Check if the arm is moving by comparing its current joints position to its latest
if (fabs(joints_current_position[0] - joints_last_position[0]) < tolerance && fabs(joints_current_position[1] - joints_last_position[1]) < tolerance)
moving_state = false;
else {
moving_state = true;
joints_last_position = joints_current_position;
}
}
When
sub1
receives a message on the
/simple_arm/joint_states
topic, the message is passed to the
joint_states_callback
in the variable
js
. The
joint_states_callback()
function checks if the current joint states provided in
js
are the same as the previous joint states, which are stored in the global
joints_last_position
variable. If the current and previous joint states are the same (up to the specified error tolerance), then the arm has stopped moving, and the
moving_state
flag is set to
False
. This flag is defined globally so as to be shared with other functions in the code. On the other hand, if the current and previous joint states are different, then the arm is still moving. In this case, the function sets
moving_state
to
true
and updates the
joints_Last_position
variable with current position data stored in
joints_current_position
.
void look_away_callback(const sensor_msgs::Image img)
{
bool uniform_image = true;
// Loop through each pixel in the image and check if its equal to the first one
for (int i = 0; i < img.height * img.step; i++) {
if (img.data[i] - img.data[0] != 0) {
uniform_image = false;
break;
}
}
// If the image is uniform and the arm is not moving, move the arm to the center
if (uniform_image == true && moving_state == false)
move_arm_center();
}
The
look_away_callback()
function receives
image data
from the
/rgb_camera/image_raw
topic. The callback function first checks if all color values in the image are the same as the color value of the first pixel. Then, if the image is uniform and the arm is not moving, the
move_arm_center()
function is called.
void move_arm_center()
{
ROS_INFO_STREAM("Moving the arm to the center");
// Request centered joint angles [1.57, 1.57]
simple_arm::GoToPosition srv;
srv.request.joint_1 = 1.57;
srv.request.joint_2 = 1.57;
// Call the safe_move service and pass the requested joint angles
if (!client.call(srv))
ROS_ERROR("Failed to call service safe_move");
}
Inside the
move_arm_center
function, a
GoToPosition
request message is created and sent using the
arm_mover/safe_move
service, moving both joint angles to
1.57
radians.